package com.tcl.logoidentify.commons;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FPCache implements Iterable<Entry<Integer, CircleQueue>> {
	private static final Logger LOGGER = LoggerFactory.getLogger(FPCache.class);
	private final int SECOND = Integer.valueOf(ConfigUtils.$("fp_cache_second", "20"));
	private final static int MSG_SIZE = 28;
	private final int QUEUE_SIZE = 25 * 2 * SECOND;
	private final ForkJoinPool forkJoinPool = new ForkJoinPool();
	private ConcurrentMap<Integer, CircleQueue> datas = new ConcurrentHashMap<Integer, CircleQueue>();
	private CircleQueue firstLevelCache = new CircleQueue(1000, MSG_SIZE);
	private boolean isFirstLevelCached = false;

	public int size() {
		return datas.size();
	}

	public FPCache() {

	}

	public FPCache(boolean isFirstLevelCached) {
		this.isFirstLevelCached = isFirstLevelCached;
	}

	public FPCache(int firstCacheSize) {
		this.isFirstLevelCached = true;
		firstLevelCache = new CircleQueue(firstCacheSize, MSG_SIZE);
	}

	public CircleQueue getQueue(Integer key) {
		CircleQueue queue = datas.get(key);
		if (queue == null) {
			queue = new CircleQueue(QUEUE_SIZE, MSG_SIZE);
			CircleQueue pre = datas.putIfAbsent(key, queue);
			return pre == null ? queue : pre;
		} else {
			return queue;
		}
	}

	@Override
	public Iterator<Entry<Integer, CircleQueue>> iterator() {
		return datas.entrySet().iterator();
	}

	public byte[] search(byte[] clientCode) {

		if (isFirstLevelCached) {
			for (byte[] serverCode : firstLevelCache) {
				if (CompareAlgorithm.compare(clientCode, serverCode))
					return serverCode;
			}
		}

		for (Entry<Integer, CircleQueue> entry : datas.entrySet()) {
			CircleQueue queue = entry.getValue();
			for (byte[] serverCode : queue) {
				if (CompareAlgorithm.compare(clientCode, serverCode)) {
					firstLevelCache.add(serverCode);
					return serverCode;
				}
			}
		}
		return null;
	}

	public byte[] forkJoinSearch(byte[] clientCode) {
		if (isFirstLevelCached) {
			for (byte[] serverCode : firstLevelCache) {
				if (CompareAlgorithm.compare(clientCode, serverCode))
					return serverCode;
			}
		}

		AtomicBoolean done = new AtomicBoolean(false);
		SearchTask task = new SearchTask(datas, clientCode, done);
		Future<byte[]> result = forkJoinPool.submit(task);
		byte[] res = null;
		try {

			res = result.get();
			firstLevelCache.add(res);
			return res;
		} catch (Exception e) {
			LOGGER.error("fork-join task error|task=" + task, e);
		}
		return null;
	}

	public static class SearchTask extends RecursiveTask<byte[]> {
		private static final long serialVersionUID = 1L;
		private Map<Integer, CircleQueue> map = null;
		private CircleQueue queue = null;
		private byte[] clientCode = null;
		AtomicBoolean done = null;

		public SearchTask(Map<Integer, CircleQueue> map, CircleQueue queue, byte[] clientCode, AtomicBoolean done) {
			this.map = map;
			this.queue = queue;
			this.clientCode = clientCode;
			this.done = done;
		}

		public SearchTask(CircleQueue queue, byte[] clientCode, AtomicBoolean done) {
			this(null, queue, clientCode, done);
		}

		public SearchTask(Map<Integer, CircleQueue> map, byte[] clientCode, AtomicBoolean done) {
			this(map, null, clientCode, done);
		}

		@Override
		protected byte[] compute() {

			if (done.get())
				return null;

			if (queue != null) {

				for (byte[] serverCode : queue) {
					if (done.get()) {
						return null;
					} else if (CompareAlgorithm.compare(clientCode, serverCode) && done.compareAndSet(false, true)) {
						return serverCode;
					}
				}

			} else if (map != null) {
				List<SearchTask> taskList = new ArrayList<SearchTask>();
				for (Entry<Integer, CircleQueue> entry : map.entrySet()) {
					SearchTask task = new SearchTask(entry.getValue(), clientCode, done);
					taskList.add(task);
				}
				for (SearchTask task : invokeAll(taskList)) {
					byte[] res = task.join();
					if (res != null) {
						return res;
					}
				}

			}
			return null;

		}
	}

}
